<!DOCTYPE html>
<html lang="zh-cn"><head>
<title>RainbowC0&#39;s Blog - C 语言宏 &#43; 内联汇编实现 MIPS 系统调用</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="description"
    content="笔者最近作业要求练习 MIPS 汇编，熟悉 MIPS 汇编代码与 C 语言代码的对应关系。然而 SPIM/MARS 仿真器不能链接共享库以调用外部函数（如 stdio.h 下的函数），只能通过系统调用实现。C 语言可以通过内联汇编（Inline Assembly）实现系统调用而不借助任何外部函数，再将内联汇编语句封装成函数或宏函数，便于 C 程序调用。 ">
<link rel="canonical" href="https://rainbowc0.gitee.io/post/macro-assembly-syscall/" />

<link rel="icon" type="image/x-icon" href="/favicon/favicon.ico">
  



	<link rel="stylesheet"
  href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/solarized-light.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>


<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/mipsasm.min.js"></script>

<script>hljs.initHighlightingOnLoad();</script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css">

<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.6/build/pure-min.css">







<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.css">



<link rel="stylesheet" href="/css/hugo-tufte.min.css">

<link rel="stylesheet"  href="/css/hugo-tufte-override.css">



</head>
<body >
        
<div id="layout" class="pure-g">
  <article class="pure-u-1">
    <header class="brand">
  <a href="https://rainbowc0.gitee.io/"><h1>RainbowC0&#39;s Blog</h1></a>
  <h2>惟精惟一，允执厥中。</h2>
  <nav class="menu">
    <ul>
    
        <li><a href="/"><i class="fas fa-home la-lg"></i>HOME</a></li>
    
        <li><a href="/post"><i class="fas fa-book" fa-lg></i>POSTS</a></li>
    
        <li><a href="/categories"><i class="fas fa-tag fa-lg"></i>CATEGORIES</a></li>
    
        <li><a href="/tags"><i class="fas fa-tags fa-lg"></i>TAGS</a></li>
    
        <li><a href="/about"><i class="fas fa-info-circle fa-lg"></i>ABOUT</a></li>
    
        <li><a href="https://gitee.com/RainbowC0/RainbowC0"><i class="fab fa-github fa-lg"></i>SOURCE CODE</a></li>
    
    </ul>
</nav>

  <hr />
</header>

    <section>
  
  <h1 class="content-title">
    
    <a href="/post/macro-assembly-syscall/">C 语言宏 &#43; 内联汇编实现 MIPS 系统调用</a>
    
  </h1>
  
    
    
      <span class="content-meta">
        
    
            
        
          <i class="fa fa-calendar"></i>
          &nbsp;Apr 12, 2024
        
    
        
          &nbsp;<i class="fa fa-clock-o"></i>
          &nbsp;3 min read
        
    
        
          <br>
          <i class="fa fa-tags"> </i>
          
            <a  href="https://rainbowc0.gitee.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA">计算机</a>
          
        
      </span>
    
  
  </section>
    
<section>
  <details closed class="toc">
    <summary>Contents</summary>
    <nav id="TableOfContents">
  <ul>
    <li><a href="#内联汇编">内联汇编</a></li>
    <li><a href="#宏函数">宏函数</a></li>
    <li><a href="#宏定义-syscall-内联汇编">宏定义 Syscall 内联汇编</a></li>
    <li><a href="#编译测试">编译测试</a></li>
  </ul>
</nav>
  </details>
</section>


    <section><p>笔者最近作业要求练习 MIPS 汇编，熟悉 MIPS 汇编代码与 C 语言代码的对应关系。然而 SPIM/MARS 仿真器不能链接共享库以调用外部函数（如 stdio.h 下的函数），只能通过系统调用实现。C 语言可以通过内联汇编（Inline Assembly）实现系统调用而不借助任何外部函数，再将内联汇编语句封装成函数或宏函数，便于 C 程序调用。</p>
<h2 id="内联汇编">内联汇编</h2>
<p>内联汇编主要借助关键字 <code>asm</code> 或 <code>__asm__</code> (C99) 实现。内敛汇编语句基本格式：</p>
<pre><code class="language-c">__asm__ [volatile](汇编语句[:[输出结果]:[输入参数][:异常检测条件]]);
</code></pre>
<p><code>volatile</code> 关键字用于防止编译器优化更改此处汇编代码；汇编语句填入汇编代码字符串；后面三类参数均可省，其中输出结果填入一个存结果的变量，输入参数填需要载入的变量或者供替换代码中占位符的有关值；异常检测可填入需要保持的寄存器，当寄存器被占用时，编译器会报错。</p>
<p>另外一个用法是用在 <code>register</code> 型变量之后，可以指定该变量对应哪个寄存器，如：</p>
<pre><code class="language-c">register int sys_id __asm__(&quot;$2&quot;) = 4;
</code></pre>
<p>这样对变量 <code>sys_id</code> 的取值/赋值等操作就等于对寄存器 <code>$2</code> 的读写操作。</p>
<p>以下是一些例子：</p>
<pre><code class="language-c">int a, b;
// a = a + b - 1;
// %0, %1, ... 就是占位符
__asm__ volatile(
    &quot;add %1,%1,%2\n\t&quot;
    &quot;addi %0,%1,-1&quot;
    :&quot;=r&quot;(a)
    :&quot;r&quot;(a),&quot;r&quot;(b));
// a = b &lt; 0;
__asm__ volatile(
    &quot;slt %0, %1, $0&quot;
    :&quot;=r&quot;(a)
    :&quot;r&quot;(b));
// printf(&quot;Hello&quot;);
register char *msg asm(&quot;$4&quot;) = &quot;Hello&quot;;
__asm__ volatile(
    &quot;jal printf&quot;
    ::&quot;r&quot;(msg));
// 此处一定要有 &quot;r&quot;(msg)，否则编译器可能会认为变量 mmm 未被使用而忽略对该变量的赋值操作
</code></pre>
<h2 id="宏函数">宏函数</h2>
<p>宏的本质就是代码段替换，只需要给一个代码段声明一个名称就可以在代码中反复使用这一代码段。代码段可以是最基础的字面常量等，也可以是稍复杂的多条语句（如宏函数）。当然，宏也可以简化一些语句，甚至可以用宏实现 <code>try-catch</code> 语句<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
<p>常见宏函数的声明形式如下：</p>
<pre><code class="language-c">// 无“返回值”型
#define 函数名([参数列表])\
{\
   代码段;\
}

// 有“返回值”型。这里用到了括号的一个语法
#define 函数名([参数列表])\
({\
    代码段;\
    返回值（右值表达式）;\
})
</code></pre>
<p>另外，参数列表是可选项，没有类型限制，甚至也可以是代码段。</p>
<h2 id="宏定义-syscall-内联汇编">宏定义 Syscall 内联汇编</h2>
<p>SPIM 仿真器的 MIPS 系统调用参数：</p>
<table>
<thead>
<tr>
<th>服务</th>
<th>系统调用代码</th>
<th>参数</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>print_int</code></td>
<td>1</td>
<td>$a0=integer</td>
<td></td>
</tr>
<tr>
<td><code>print_float</code></td>
<td>2</td>
<td>$f12=float</td>
<td></td>
</tr>
<tr>
<td><code>print_double</code></td>
<td>3</td>
<td>$f12=double</td>
<td></td>
</tr>
<tr>
<td><code>print_string</code></td>
<td>4</td>
<td>$a0=string</td>
<td></td>
</tr>
<tr>
<td><code>read_int</code></td>
<td>5</td>
<td></td>
<td>integer (in $v0)</td>
</tr>
<tr>
<td><code>read_float</code></td>
<td>6</td>
<td></td>
<td>float (in $v0)</td>
</tr>
<tr>
<td><code>read_double</code></td>
<td>7</td>
<td></td>
<td>double (in $v0)</td>
</tr>
<tr>
<td><code>read_string</code></td>
<td>8</td>
<td>$a0=buffer, $a1=length</td>
<td></td>
</tr>
<tr>
<td><code>sbrk</code></td>
<td>9</td>
<td>$a0=amount</td>
<td>address (in $v0)</td>
</tr>
<tr>
<td><code>exit</code></td>
<td>10</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>print_char</code></td>
<td>11</td>
<td>$a0=char</td>
<td></td>
</tr>
<tr>
<td><code>read_char</code></td>
<td>12</td>
<td></td>
<td>char (in $v0)</td>
</tr>
<tr>
<td><code>open</code></td>
<td>13</td>
<td>$a0=filename(string), $a1=flags, $a2=mode</td>
<td>file descriptor (in $a0)</td>
</tr>
<tr>
<td><code>read</code></td>
<td>14</td>
<td>$a0=file descriptor, $a1=buffer, $a2=length</td>
<td>num chars read (in $a0)</td>
</tr>
<tr>
<td><code>write</code></td>
<td>15</td>
<td>$a0=file descriptor, $a1=buffer, $a2=length</td>
<td>num chars written (in $a0)</td>
</tr>
<tr>
<td><code>close</code></td>
<td>16</td>
<td>$a0=file descriptor</td>
<td></td>
</tr>
<tr>
<td><code>exit2</code></td>
<td>17</td>
<td>$a0=result</td>
<td></td>
</tr>
</tbody>
</table>
<p>用上述两种宏函数定义方式定义其中几个常用的系统调用，如下：</p>
<pre><code class="language-c">#define sys_open(pth, fg) ({\
    register int _ID_ __asm__(&quot;$2&quot;) = 13, _FG_ __asm__(&quot;$5&quot;) = fg;\
    register char *_PTH_ __asm__(&quot;$4&quot;) = pth;\
    __asm__ volatile(&quot;syscall&quot;\
    :&quot;=r&quot;(_ID_):&quot;r&quot;(_ID_),&quot;r&quot;(_PTH_),&quot;r&quot;(_FG_));\
    _ID_;})

#define sys_print_string(str) {\
    register int _ID_ __asm__(&quot;$2&quot;) = 4;\
    register char *_STR_ __asm__(&quot;$4&quot;) = str;\
    __asm__ volatile(&quot;syscall&quot;\
    ::&quot;r&quot;(_ID_),&quot;r&quot;(_STR_));}

#define sys_print_int(i) {\
    register int _ID_ __asm__(&quot;$2&quot;) = 1, _I_ __asm__(&quot;$4&quot;) = i;\
    __asm__ volatile(&quot;syscall&quot;::&quot;r&quot;(_ID_),&quot;r&quot;(_I_));}

#define sys_read_int() ({\
    register int _ID_ __asm__(&quot;$2&quot;) = 5;\
    __asm__ volatile(&quot;syscall&quot;\
    :&quot;=r&quot;(_ID_):&quot;r&quot;(_ID_));\
    _ID_;})

#define sys_read(fd, buf, len) ({\
    register int _ID_ __asm__(&quot;$2&quot;) = 14, _FD_ __asm__(&quot;$4&quot;) = fd, _LEN_ __asm__(&quot;$6&quot;) = len;\
    register char *_BUF_ __asm__(&quot;$5&quot;) = buf;\
    __asm__ volatile(&quot;syscall&quot;\
    :&quot;=r&quot;(_ID_):&quot;r&quot;(_ID_),&quot;r&quot;(_FD_),&quot;r&quot;(_BUF_),&quot;r&quot;(_LEN_));\
    _ID_;})

#define sys_close(fd) {\
    register int _ID_ __asm__(&quot;$2&quot;) = 16, _FD_ __asm__(&quot;$4&quot;) = fd;\
    __asm__ volatile(&quot;syscall&quot;::&quot;r&quot;(_ID_),&quot;r&quot;(_FD_));}

#define sys_exit() {\
    register int _ID_ __asm__(&quot;$2&quot;) = 10;\
    __asm__ volatile(&quot;syscall&quot;::&quot;r&quot;(_ID_));}
</code></pre>
<h2 id="编译测试">编译测试</h2>
<p>老师推荐用在线平台 <a href="https://godbolt.org">https://godbolt.org</a> 编译测试，其实本地用 <code>mips-linux-gnu-gcc</code> 交叉编译也行。将以上宏定义存为头文件 mips-syscall.h，然后在代码中引用，进行简单的测试：</p>
<pre><code class="language-c">#include &quot;mips-syscall.h&quot;

void main() {
  sys_print_string(&quot;Input a number: &quot;);
  int n = sys_read_int();
  sys_print_string(&quot;The number is &quot;);
  sys_print_int(n);
  sys_exit();
}
</code></pre>
<p>由于 SPIM/MARS 仿真器的执行入口和一般程序不太一样，而且需要调用 <code>exit</code> 来结束程序，所以以上代码的驻韩数写法比较怪。</p>
<p>本地交叉编译，编译器 mips-linux-gnu-gcc 12.3.0，编译参数 <code>-O2 -S -o m.s</code>，去掉不相关字段：</p>
<pre><code class="language-mips">	.data
$LC0:
	.ascii	&quot;Input a number: \000&quot;
$LC1:
	.ascii	&quot;The number is \000&quot;
    .text
main:
	lw	$4,%got($LC0)($28)
	li	$2,4			# 0x4
	addiu	$4,$4,%lo($LC0)
	syscall
	li	$2,5			# 0x5
	syscall
	lw	$4,%got($LC1)($28)
    move    $3,$2
	li	$2,4			# 0x4
	addiu	$4,$4,%lo($LC1)
	syscall
	li	$2,1			# 0x1
	move	$4,$3
	syscall
	li	$2,10			# 0xa
	syscall
	jr	$31
</code></pre>
<p>可以看到已经成功编译，同时宏函数也都被替换为相应的系统调用。再经过一些调整后得到 MARS 可用的代码，运行测试，结果如下：</p>
<pre><code class="language-plain">Input a number: 9
The number is 9
-- program is finished running --
</code></pre>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://zhuanlan.zhihu.com/p/245642367">https://zhuanlan.zhihu.com/p/245642367</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div></section>
    <section>
      <footer class="page-footer">
		<hr>

    <div class="previous-post" style="display:inline-block;">
      
      <a class="link-reverse" href="https://rainbowc0.gitee.io/post/github-page-to-bing/?ref=footer">« 将 Github Pages 个人博客录入搜索引擎（以 Bing 为例）</a>
      
    </div>

    <div class="next-post", style="display:inline-block;float:right;">
      
    </div>

		<ul class="page-footer-menu">

      
      

      
      <li><a href="https://github.com/RainbowC0"><i class='fab fa-github fa-lg'></i></a></li>
      

      

      

      

      

      

      

      

      

      

      
      
      
		</ul>

  

	<div class="copyright">
	<p>
    
      &copy; 2024
    .
    All rights reserved.
    
  </p>
</div>
</footer>

    </section>
  </article>
</div>

    </body>

</html>